#!/usr/bin/python3
# This is a GDB plugin.
# Usage:
# gdb --batch -ex 'python exec(open("THIS_FILE").read())' -ex run -ex abrt-exploitable PROG
# or
# gdb --batch -ex 'python exec(open("THIS_FILE").read())' -ex 'core COREDUMP' -ex abrt-exploitable

import sys
import os
import signal
import re
import gettext
import locale
import gdb

GETTEXT_PROGNAME = "abrt"
_ = gettext.gettext

def init_gettext():
    try:
        locale.setlocale(locale.LC_ALL, "")
    except locale.Error:
        os.environ['LC_ALL'] = 'C'
        locale.setlocale(locale.LC_ALL, "")
    gettext.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale')
    gettext.textdomain(GETTEXT_PROGNAME)

_WRITES_ALWAYS = -1
_WRITES_IF_MEMREF = -2

_x86_writing_instr = {
    # insn:N, where N:
    # -1: this insn always writes to memory
    # -2: writes to memory if any operand is a memory operand
    # 2:  writes to memory if 2nd (or later) operand is a memory operand
    #
    # Two-operand insns
    "add": 2,
    "adc": 2,
    "sub": 2,
    "sbb": 2,
    "and": 2,
    "xor": 2,
    "or": 2,
    "xadd": 2,
    "cmpxchg": 2,
    # One-operand insns. Can use 1 or _WRITES_IF_MEMREF
    "inc": _WRITES_IF_MEMREF,
    "dec": _WRITES_IF_MEMREF,
    "neg": _WRITES_IF_MEMREF,
    "not": _WRITES_IF_MEMREF,
    "pop": _WRITES_IF_MEMREF,
    # "Set byte on condition". One-operand insns.
    "seta": _WRITES_IF_MEMREF,
    "setae": _WRITES_IF_MEMREF,
    "setb": _WRITES_IF_MEMREF,
    "setbe": _WRITES_IF_MEMREF,
    "setc": _WRITES_IF_MEMREF,
    "sete": _WRITES_IF_MEMREF,
    "setg": _WRITES_IF_MEMREF,
    "setge": _WRITES_IF_MEMREF,
    "setl": _WRITES_IF_MEMREF,
    "setle": _WRITES_IF_MEMREF,
    "setna": _WRITES_IF_MEMREF,
    "setnae": _WRITES_IF_MEMREF,
    "setnb": _WRITES_IF_MEMREF,
    "setnbe": _WRITES_IF_MEMREF,
    "setnc": _WRITES_IF_MEMREF,
    "setne": _WRITES_IF_MEMREF,
    "setng": _WRITES_IF_MEMREF,
    "setnge": _WRITES_IF_MEMREF,
    "setnl": _WRITES_IF_MEMREF,
    "setnle": _WRITES_IF_MEMREF,
    "setno": _WRITES_IF_MEMREF,
    "setnp": _WRITES_IF_MEMREF,
    "setns": _WRITES_IF_MEMREF,
    "setnz": _WRITES_IF_MEMREF,
    "seto": _WRITES_IF_MEMREF,
    "setp": _WRITES_IF_MEMREF,
    "setpe": _WRITES_IF_MEMREF,
    "setpo": _WRITES_IF_MEMREF,
    "sets": _WRITES_IF_MEMREF,
    "setz": _WRITES_IF_MEMREF,
    # Shifts.
    # sarl $2,(%rcx)
    # sarl (%rax) - *implicit* operand (shift count) 1.
    # shld 11,%ecx,(%rdi) - *third* operand is r/m.
    # Luckily, any memory operand is a destination, can use _WRITES_IF_MEMREF.
    "shl": _WRITES_IF_MEMREF,
    "shr": _WRITES_IF_MEMREF,
    "sal": _WRITES_IF_MEMREF,
    "sar": _WRITES_IF_MEMREF,
    "rol": _WRITES_IF_MEMREF,
    "ror": _WRITES_IF_MEMREF,
    "rcl": _WRITES_IF_MEMREF,
    "rcr": _WRITES_IF_MEMREF,
    "shld": _WRITES_IF_MEMREF,
    "shrd": _WRITES_IF_MEMREF,
    # Bit tests. Any memory operand is a destination, can use _WRITES_IF_MEMREF.
    "bts": _WRITES_IF_MEMREF,
    "btr": _WRITES_IF_MEMREF,
    "btc": _WRITES_IF_MEMREF,
    # One-operand (register pair is another, implicit operand).
    "cmpxchg8b": _WRITES_IF_MEMREF,
    "cmpxchg16b": _WRITES_IF_MEMREF,

    # Either mem operand indicates write to mem.
    "xchg": _WRITES_IF_MEMREF,

    # String store insns.
    # Look similar to widening signed move "movs[bwl][wlq]",
    # but aliasing doesn't happen since widening move has two siffixes
    "movs": _WRITES_ALWAYS,
    "stos": _WRITES_ALWAYS,
    # Widening moves never store to mem.
    # May look like we need to list them because otherwise they get caught
    # by "movXXX", but thankfully their 2nd operand is never a memory reference,
    # which "movXXX" wildcard checks.
    #"mov[sz][bwl][wlq]":0,

    # One-operand insn.
    # These are system insns, but they do NOT cause exception in userspace.
    "smsw": _WRITES_IF_MEMREF,
    "sgdt": _WRITES_IF_MEMREF,
    "sidt": _WRITES_IF_MEMREF,
    "sldt": _WRITES_IF_MEMREF,
    "str": _WRITES_IF_MEMREF,

    # FPU/SIMD madness follows.

    # FPU store insns. One-operand.
    "fsts": _WRITES_IF_MEMREF,
    "fstl": _WRITES_IF_MEMREF,
    #"fstt" doesn't exist
    "fstps": _WRITES_IF_MEMREF,
    "fstpl": _WRITES_IF_MEMREF,
    "fstpt": _WRITES_IF_MEMREF,
    # Saving state. One-operand insns.
    "fstcw": _WRITES_IF_MEMREF,
    "fnstcw": _WRITES_IF_MEMREF,
    "fstsw": _WRITES_IF_MEMREF,
    "fnstsw": _WRITES_IF_MEMREF,
    "fstenv": _WRITES_IF_MEMREF,
    "fnstenv": _WRITES_IF_MEMREF,
    "fsave": _WRITES_IF_MEMREF,
    "fnsave": _WRITES_IF_MEMREF,
    "fxsave": _WRITES_IF_MEMREF,
    "xsave": _WRITES_IF_MEMREF,
    "xsaveopt": _WRITES_IF_MEMREF,
    "fsave64": _WRITES_IF_MEMREF,
    "fnsave64": _WRITES_IF_MEMREF,
    "fxsave64": _WRITES_IF_MEMREF,
    "xsave64": _WRITES_IF_MEMREF,
    "xsaveopt64": _WRITES_IF_MEMREF,
    "stmxcsr": _WRITES_IF_MEMREF,
    "vstmxcsr": _WRITES_IF_MEMREF,
    # SIMD store insns.
    # Three-operand insns. Any memory operand is a destination.
    "vcvtps2ph": _WRITES_IF_MEMREF,
    "extractps": _WRITES_IF_MEMREF,
    "vextractps": _WRITES_IF_MEMREF,
    #[v]extractpd does not exist
    "vextractf128": _WRITES_IF_MEMREF,
    "vextracti128": _WRITES_IF_MEMREF,
    "pextr": _WRITES_IF_MEMREF,       # covers pextr[bwq]
    "pextrd": _WRITES_IF_MEMREF,
    "vpextr": _WRITES_IF_MEMREF,
    "vpextrd": _WRITES_IF_MEMREF,
    "vmaskmovpd": _WRITES_IF_MEMREF,
    "vmaskmovps": _WRITES_IF_MEMREF,
    "vpmaskmovd": _WRITES_IF_MEMREF,
    "vpmaskmovq": _WRITES_IF_MEMREF,
    # These insns have implicit (%edi) dest operand:
    "maskmovq": _WRITES_ALWAYS,    # mmx version
    "maskmovdqu": _WRITES_ALWAYS,
    "vmaskmovdqu": _WRITES_ALWAYS,

    # check binutils/gas/testsuite/gas/i386/* for more weird insns
    # Instruction Set Reference, A-M and N-Z:
    # http://download.intel.com/products/processor/manual/253666.pdf
    # http://download.intel.com/products/processor/manual/253667.pdf
    # SSE4:
    # http://software.intel.com/sites/default/files/m/0/3/c/d/4/18187-d9156103.pdf
    # Instruction Set Extensions:
    # http://download-software.intel.com/sites/default/files/319433-014.pdf
    # Xeon Phi:
    # http://download-software.intel.com/sites/default/files/forum/278102/327364001en.pdf

    #"[v]movXXX" - special-cased in the code
    "mov": 2

    # Note: stack-writing instructions are omitted
}

_x86_pushing_instr = (
    "push",
    "pusha",
    "pushf",
    "enter",
    "call",
    "lcall"
)

_x86_intdiv_instr = ("div", "idiv")

_x86_jumping_instr = (
    "jmp",  # indirect jumps/calls with garbage data
    "call", # call: also possible that stack is exhausted (infinite recursion)
    "ljmp",
    "lcall",
    # Yes, lret/iret isn't used in normal userspace code,
    # but it does work (compile with "gcc -nostartfiles -nostdlib -m32"):
    #
    #_start: .globl  _start
    #        pushf
    #        push    %cs
    #        push    $next
    #        iret            # lret or ret would work too
    #next:
    #        movl    $42, %ebx
    #        movl    $1, %eax
    #        int     $0x80   # exit(42)
    #
    "iret",
    "lret",
    "ret"
)

# stack was smashed if we crash on one of these
_x86_return_instr = ("iret", "lret", "ret")

_x86_mem_op1_regex = re.compile("^((-?0x)|[(])")
_x86_mem_op2_regex = re.compile("[,:]((-?0x)|[(])")

def _x86_fetch_insn_from_table(ins, table):
    if not ins:
        return None
    if ins in table:
        if type(table) == dict:
            return table[ins]
        return ins
    # Drop common byte/word/long/quad suffix and try again
    if ins[-1] in ("b", "w", "l", "q"):
        ins = ins[:-1]
        if ins in table:
            if type(table) == dict:
                return table[ins]
            return ins
    return None


class SignalAndInsn:

    def x86_instruction_is_store(self):
        operand = _x86_fetch_insn_from_table(self.mnemonic, _x86_writing_instr)
        if not operand:
            if not self.mnemonic:
                return False
            # There are far too many SSE store instructions,
            # don't want to pollute the table with them.
            # Special-case the check for MOVxxx
            # and its SIMD cousins VMOVxxx:
            if self.mnemonic[:3] != "mov" and self.mnemonic[:4] != "vmov":
                return False
            operand = 2

        if operand == _WRITES_ALWAYS:  # no need to check operands, it's a write
            return True

        # Memory operands look like this: [%seg:][[-]0xHEXNUM][(%reg[,...])]
        # Careful with immediate operands which are $0xHEXNUM
        # and FPU register references which are st(N).
        if _x86_mem_op1_regex.search(self.operands):
            mem_op_pos = 0
        else:
            match = _x86_mem_op2_regex.search(self.operands)
            if not match:
                return False # no memory operands
            mem_op_pos = match.start() + 1

        if operand == _WRITES_IF_MEMREF:  # any mem operand indicates write
            return True

        comma = self.operands.find(",")
        if mem_op_pos < comma:
            # "%cs:0x0(%rax,%rax,1),foo" - 1st operand is memory
            # "%cs:0x0(%rax),foo" - 1st operand is memory
            memory_operand = 1
        elif comma < 0:
            # "%cs:0x0(%rax)" - 1st operand is memory
            memory_operand = 1
        else:
            # mem_op_pos is after comma
            # "foo,%cs:0x0(%rax,%rax,1)" - 2nd operand is memory
            # (It also can be a third, fourth etc operand)
            memory_operand = 2

        if operand == memory_operand:
            return True
        return False

    def x86_get_instruction(self):
        try:
            # just "disassemble $pc" won't work if $pc doesn't point
            # inside a known function
            raw_instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True)
        except gdb.error:
            # For example, if tracee already exited normally.
            # Another observed case is if $pc points to unmapped area.
            # We get "Python Exception <class 'gdb.error'> No registers"
            return

        instructions = []
        current = None
        for line in raw_instructions.split("\n"):
            # line can be:
            # "Dump of assembler code from 0xAAAA to 0xBBBB:"
            # "[=>] 0x00000000004004dc[ <+0>]:  push   %rbp"
            #   (" <+0>" part is present when we run on a live process,
            #   on coredump it is absent)
            # "End of assembler dump."
            # "" (empty line)
            if line.startswith("=>"):
                line = line[2:]
                current = len(instructions)
            line = line.split(":", 1)
            if len(line) < 2:        # no ":"?
                continue
            line = line[1]           # drop "foo:"
            line = line.strip()      # drop leading/trailing whitespace
            if line:
                instructions.append(line)
        if current == None:
            # we determined that $pc points to a bad address,
            # which is an interesting fact.
            return

        # There can be a disasm comment: "insn op,op,op  # comment";
        # strip it, and whitespace on both ends:
        t = instructions[current].split("#", 1)[0].strip()
        self.current_instruction = t
        # Strip prefixes:
        while True:
            t = t.split(None, 1)
            self.mnemonic = t[0]
            if len(t) < 2:
                break
            if self.mnemonic.startswith("rex."):
                t = t[1]
                continue
            if self.mnemonic in (
                    "data32", "data16", "addr32", "addr16", "rex",
                    "cs", "ds", "es", "ss", "fs", "gs",
                    "lock", "rep", "repz", "repnz", "xacquire", "xrelease"
            ):
                t = t[1]
                continue
            # First word isn't a prefix -> we found the insn word
            self.operands = t[1]
            break

        self.instruction_is_pushing = (_x86_fetch_insn_from_table(self.mnemonic, _x86_pushing_instr) is not None)
        self.instruction_is_division = (_x86_fetch_insn_from_table(self.mnemonic, _x86_intdiv_instr) is not None)
        self.instruction_is_branch = (_x86_fetch_insn_from_table(self.mnemonic, _x86_jumping_instr) is not None)
        self.instruction_is_return = (_x86_fetch_insn_from_table(self.mnemonic, _x86_return_instr) is not None)
        self.instruction_is_store = self.x86_instruction_is_store()


    def ppc_get_instruction(self):
        try:
            # just "disassemble $pc" won't work if $pc doesn't point
            # inside a known function
            raw_instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True)
        except gdb.error:
            # For example, if tracee already exited normally.
            # Another observed case is if $pc points to unmapped area.
            # We get "Python Exception <class 'gdb.error'> No registers"
            return

        instructions = []
        current = None
        for line in raw_instructions.split("\n"):
            # line can be:
            # "Dump of assembler code from 0xAAAA to 0xBBBB:"
            # "[=>] 0x00000000004004dc[ <+0>]:  push   %rbp"
            #   (" <+0>" part is present when we run on a live process,
            #   on coredump it is absent)
            # "End of assembler dump."
            # "" (empty line)
            if line.startswith("=>"):
                line = line[2:]
                current = len(instructions)
            line = line.split(":", 1)
            if len(line) < 2:        # no ":"?
                continue
            line = line[1]           # drop "foo:"
            line = line.strip()      # drop leading/trailing whitespace
            if line:
                instructions.append(line)
        if current is None:
            # we determined that $pc points to a bad address,
            # which is an interesting fact.
            return

        # There can be a disasm comment: "insn op,op,op  # comment";
        # strip it, and whitespace on both ends:
        t = instructions[current].split("#", 1)[0].strip()
        self.current_instruction = t
        # Split it into mnemonic and operands
        t = t.split(None, 1)
        self.mnemonic = t[0]
        if len(t) > 1:
            self.operands = t[1]

        self.instruction_is_store = self.mnemonic.startswith("st")
        self.instruction_is_branch = self.mnemonic.startswith("b")
        self.instruction_is_pushing = (self.instruction_is_store and "(r1)" in self.operands)
        # Looks like div[o] insns on ppc don't cause exceptions
        # (need to check whether, and how, FPE is generated)
        #self.instruction_is_division =
        # On ppc, return insn is b[cond]lr. TODO: is cond form ever used by gcc?
        self.instruction_is_return = (self.mnemonic == "blr")


    def get_instruction(self):
        self.current_instruction = None
        self.mnemonic = None
        self.operands = ""
        self.instruction_is_division = None
        self.instruction_is_store = None
        self.instruction_is_pushing = None
        self.instruction_is_return = None
        self.instruction_is_branch = None
        try:
            arch = gdb.execute("show architecture", to_string=True)
            # Examples of the string we get:
            # The target architecture is set automatically (currently i386)
            # The target architecture is set automatically (currently i386:x86-64)
            # The target architecture is set automatically (currently powerpc:common64)
            if " i386" in arch:
                return self.x86_get_instruction()
            if " powerpc" in arch:
                return self.ppc_get_instruction()
        except gdb.error:
            return

    def get_signal(self):
        self.signo = None
        self.si_code = None
        try:
            # Requires new kernels which record complete siginfo
            # in coredumps (Linux 3.9 still don't have it),
            # and new gdb:
            sig = gdb.parse_and_eval("$_siginfo.si_signo")
            code = gdb.parse_and_eval("$_siginfo.si_code")
            # Requires patched gdb:
            #sig = gdb.parse_and_eval("$_signo")
            #
            # type(sig) = <type 'gdb.Value'>, convert to plain int:
            self.signo = int(sig)
            self.si_code = int(code)
        except gdb.error:
            # "Python Exception <class 'gdb.error'>
            #  Attempt to extract a component of a value that is not a structure"
            # Possible reasons why $_siginfo doesn't exist:
            # program is still running, program exited normally,
            # we work with a coredump from an old kernel.
            #
            # Lets see whether we are running from the abrt and it
            # provided us with signal number. Horrible hack :(
            #
            try:
                self.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
            except KeyError:
                return False
        return True

    #Our initial set of testing will use the list Apple included in their
    #CrashWrangler announcement:
    #
    #Exploitable if:
    #        Crash on write instruction
    #        Crash executing invalid address
    #        Crash calling an invalid address
    #        Crash accessing an uninitialized or freed pointer as indicated by
    #            using the MallocScribble environment variable
    #        Illegal instruction exception
    #        Abort due to -fstack-protector, _FORTIFY_SOURCE, heap corruption
    #            detected
    #        Stack trace of crashing thread contains certain functions such as
    #            malloc, free, szone_error, objc_MsgSend, etc.
    def is_exploitable(self):
        self.exploitable_rating = 3
        self.exploitable_desc = ""

        # siginfo.si_code:
        # If <= 0, then it's not a crash:
        #  SI_ASYNCNL = -60 /* asynch name lookup completion */
        #  SI_TKILL   = -6  /* tkill (and tgkill?) */
        #  SI_SIGIO   = -5  /* queued SIGIO */
        #  SI_ASYNCIO = -4  /* AIO completion */
        #  SI_MESGQ   = -3  /* real time mesq state change */
        #  SI_TIMER   = -2  /* timer expiration (timer_create() with SIGEV_SIGNAL) */
        #  SI_QUEUE   = -1  /* sigqueue */
        #  SI_USER    = 0   /* kill, sigsend */
        # Crashes have si_code > 0:
        # testDivideByZero:   SIGFPE  si_code=FPE_INTDIV(1), si_addr=0x40054a
        # x86-64 opcode 0x62: SIGILL  si_code=ILL_ILLOPN(2), si_addr=0x40053f
        # x86-64 priv.insn.:  SIGSEGV si_code=SI_KERNEL(128), si_addr=0
        # testExecuteInvalid: SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x1c2404000
        # testStackBufferOverflow ("ret" to bad address):
        #                     SIGSEGV si_code=SI_KERNEL(128), si_addr=0
        # testStackRecursion: SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x7fff4c216d28
        # testWriteRandom:    SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x1eb004004
        # However:
        # Keyboard signals (^C INT, ^\ QUIT, ^Z TSTP) also have si_code=SI_KERNEL.
        # SIGWINCH has si_code=SI_KERNEL.
        # SIGALRM from alarm(N) has si_code=SI_KERNEL.
        # Surprisingly, SIGPIPE has si_code=SI_USER!
        if self.si_code is not None:
            # Filter out user-generated signals:
            if self.si_code == -6 or self.si_code == -1:  # SI_TKILL/SI_QUEUE
                self.exploitable_rating = 0
                self.exploitable_desc = _("Signal sent by userspace code")
                return
            if self.si_code < 0:
                self.exploitable_rating = 0
                self.exploitable_desc = _("Signal sent by timer/IO/async event")
                return
            # Unfortunately, this isn't reliable to flag user-sent signals:
            # not only SIGPIPE, but some other kernel signals have SI_USER
            # (grep kernel sources for "send_sig(sig, ..., 0)").
            # At least we know it's not a crash.
            if self.si_code == 0:  # SI_USER
                self.exploitable_rating = 0
                self.exploitable_desc = _("Signal has siginfo.si_code = SI_USER")
                # Special case (a kernel buglet?)
                if self.signo == signal.SIGPIPE:
                    self.exploitable_desc = _("Signal due to write to closed pipe")
                return
            # And kernel-generated ones:
            if self.si_code == 0x80:  # SI_KERNEL
                if self.signo in (signal.SIGINT, signal.SIGQUIT, signal.SIGTSTP):
                    self.exploitable_rating = 0
                    self.exploitable_desc = _("Signal sent by keyboard")
                    return
                if self.signo in (signal.SIGTTIN, signal.SIGTTOU, signal.SIGHUP):
                    self.exploitable_rating = 0
                    self.exploitable_desc = _("Job control signal sent by kernel")
                    return
                if self.signo == signal.SIGWINCH:
                    self.exploitable_rating = 0
                    self.exploitable_desc = _("Signal sent by window resize")
                    return
                if self.signo == signal.SIGALRM:
                    self.exploitable_rating = 0
                    self.exploitable_desc = _("Signal sent by alarm(N) expiration")
                    return
                # else: Can't rule out "crash" signal: may be FPE/ILL/BUS/SEGV.
                # Fall through into signo/insn analysis.
            # else: We are here if signal was not from user and not SI_KERNEL.
            # Fall through into signo/insn analysis.
        # else: We are here if si_code isn't known.
        # Fall through into signo/insn analysis.

        # Guessing here... it might be kill(2) as well.
        # Should I add "Likely ..." to the descriptions?
        if self.signo in (signal.SIGINT, signal.SIGQUIT, signal.SIGTSTP):
            self.exploitable_rating = 0
            self.exploitable_desc = _("Signal sent by keyboard")
            return
        if self.signo in (signal.SIGTTIN, signal.SIGTTOU, signal.SIGHUP):
            self.exploitable_rating = 0
            self.exploitable_desc = _("Job control signal sent by kernel")
            return
        if self.signo == signal.SIGPIPE:
            self.exploitable_rating = 0
            self.exploitable_desc = _("Signal due to write to broken pipe")
            return
        if self.signo == signal.SIGWINCH:
            self.exploitable_rating = 0
            self.exploitable_desc = _("Signal sent by window resize")
            return
        if self.signo == signal.SIGALRM:
            self.exploitable_rating = 0
            self.exploitable_desc = _("Signal sent by alarm(N) expiration")
            return

        # Which signals can coredump?
        # SIGABRT Abort signal from abort(3)
        # SIGQUIT Quit from keyboard
        # SIGXCPU CPU time limit exceeded
        # SIGXFSZ File size limit exceeded
        # SIGTRAP Trace/breakpoint trap
        # SIGSYS  Bad argument to routine (SVr4)
        # SIGFPE  Floating point exception
        # SIGILL  Illegal Instruction
        # SIGSEGV Invalid memory reference
        # SIGBUS  Bus error (bad memory access)
        if self.signo == signal.SIGABRT:
            self.exploitable_rating = 0
            self.exploitable_desc = _("ABRT signal (abort() was called?)")
            return
        # Already handled above:
        #if self.signo == signal.SIGQUIT:
        #    self.exploitable_rating = 0
        #    self.exploitable_desc = _("QUIT signal (Ctrl-\\ pressed?)")
        #    return
        if self.signo == signal.SIGXCPU:
            self.exploitable_rating = 0
            self.exploitable_desc = _("XCPU signal (over CPU time limit)")
            return
        if self.signo == signal.SIGXFSZ:
            self.exploitable_rating = 0
            self.exploitable_desc = _("XFSZ signal (over file size limit)")
            return
        if self.signo == signal.SIGTRAP:
            self.exploitable_rating = 0
            self.exploitable_desc = _("TRAP signal (can be a bug in a debugger/tracer)")
            return
        if self.signo == signal.SIGSYS:
            self.exploitable_rating = 1
            self.exploitable_desc = _("SYS signal (unknown syscall was called?)")
            return

        if self.signo == signal.SIGFPE:
            self.exploitable_rating = 1
            self.exploitable_desc = _("Arithmetic exception")
            # 1 is FPE_INTDIV
            if self.si_code == 1 or self.instruction_is_division:
                self.exploitable_rating = 0
                self.exploitable_desc = _("Division by zero")
            return
        if self.signo == signal.SIGILL:
            self.exploitable_rating = 5
            self.exploitable_desc = _("Illegal instruction (jump to a random address?)")
            return

        if self.signo != signal.SIGSEGV and self.signo != signal.SIGBUS:
            self.exploitable_rating = 2
            # Pity that we can't give a more descriptive explanation
            self.exploitable_desc = _("Non-crash related signal")
            return

        if self.instruction_is_pushing:
            self.exploitable_rating = 4
            self.exploitable_desc = _("Stack overflow")
            return
        if self.instruction_is_store:
            self.exploitable_rating = 6
            self.exploitable_desc = _("Write to an invalid address")
            return
        if self.instruction_is_return:
            self.exploitable_rating = 7
            self.exploitable_desc = _("Subroutine return to an invalid address (corrupted stack?)")
            return
        # Note: we check "ret" first, _then_ jumps.
        # Corrupted stack is different from corrupted data.
        if self.instruction_is_branch:
            self.exploitable_rating = 6
            self.exploitable_desc = _("Jump to an invalid address")
            return
        if not self.current_instruction:
            self.exploitable_rating = 6
            self.exploitable_desc = _("Jump to an invalid address")
            return
        if self.signo == signal.SIGBUS:
            self.exploitable_rating = 5
            self.exploitable_desc = _("Access past the end of mapped file, invalid address, unaligned access, etc.")
            return
        # default values remain (e.g. description is "")

class AbrtExploitable(gdb.Command):
    "Analyze a crash to determine exploitability"
    def __init__(self):
        super(AbrtExploitable, self).__init__(
                "abrt-exploitable",
                gdb.COMMAND_SUPPORT, # command class
                gdb.COMPLETE_NONE,   # completion method
                False  # => it's not a prefix command
        )
        init_gettext()

    # Called when the command is invoked from GDB
    def invoke(self, args, from_tty):
        si = SignalAndInsn()
        if not si.get_signal():
            sys.stderr.write(_("Can't get signal no and do exploitability analysis\n"))
            return
        si.get_instruction()
        min_rating = 0
        if args:
            args = args.split(None, 1)
            min_rating = int(args[0])
        si.is_exploitable()
        if si.exploitable_desc:
            if si.exploitable_rating >= min_rating:
                f = sys.stdout
                if args and len(args) > 1:
                    f = open(args[1], 'w')
                f.write(_("Likely crash reason: ") + si.exploitable_desc + "\n")
                f.write(_("Exploitable rating (0-9 scale): ") + str(si.exploitable_rating) + "\n")
                if si.current_instruction:
                    f.write(_("Current instruction: ") + si.current_instruction + "\n")
        else:
            sys.stderr.write(_("Exploitability analysis came up empty\n"))

AbrtExploitable()
